{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N+,O+,P+,Q-,R-,S-,T-,U-,V+,W-,X+,Y+,Z1}
{$MINSTACKSIZE $00004000}
{$MAXSTACKSIZE $00100000}
{$IMAGEBASE $00400000}
{$APPTYPE GUI}
{$WARN SYMBOL_DEPRECATED ON}
{$WARN SYMBOL_LIBRARY ON}
{$WARN SYMBOL_PLATFORM ON}
{$WARN UNIT_LIBRARY ON}
{$WARN UNIT_PLATFORM ON}
{$WARN UNIT_DEPRECATED ON}
{$WARN HRESULT_COMPAT ON}
{$WARN HIDING_MEMBER ON}
{$WARN HIDDEN_VIRTUAL ON}
{$WARN GARBAGE ON}
{$WARN BOUNDS_ERROR ON}
{$WARN ZERO_NIL_COMPAT ON}
{$WARN STRING_CONST_TRUNCED ON}
{$WARN FOR_LOOP_VAR_VARPAR ON}
{$WARN TYPED_CONST_VARPAR ON}
{$WARN ASG_TO_TYPED_CONST ON}
{$WARN CASE_LABEL_RANGE ON}
{$WARN FOR_VARIABLE ON}
{$WARN CONSTRUCTING_ABSTRACT ON}
{$WARN COMPARISON_FALSE ON}
{$WARN COMPARISON_TRUE ON}
{$WARN COMPARING_SIGNED_UNSIGNED ON}
{$WARN COMBINING_SIGNED_UNSIGNED ON}
{$WARN UNSUPPORTED_CONSTRUCT ON}
{$WARN FILE_OPEN ON}
{$WARN FILE_OPEN_UNITSRC ON}
{$WARN BAD_GLOBAL_SYMBOL ON}
{$WARN DUPLICATE_CTOR_DTOR ON}
{$WARN INVALID_DIRECTIVE ON}
{$WARN PACKAGE_NO_LINK ON}
{$WARN PACKAGED_THREADVAR ON}
{$WARN IMPLICIT_IMPORT ON}
{$WARN HPPEMIT_IGNORED ON}
{$WARN NO_RETVAL ON}
{$WARN USE_BEFORE_DEF ON}
{$WARN FOR_LOOP_VAR_UNDEF ON}
{$WARN UNIT_NAME_MISMATCH ON}
{$WARN NO_CFG_FILE_FOUND ON}
{$WARN MESSAGE_DIRECTIVE ON}
{$WARN IMPLICIT_VARIANTS ON}
{$WARN UNICODE_TO_LOCALE ON}
{$WARN LOCALE_TO_UNICODE ON}
{$WARN IMAGEBASE_MULTIPLE ON}
{$WARN SUSPICIOUS_TYPECAST ON}
{$WARN PRIVATE_PROPACCESSOR ON}
{$WARN UNSAFE_TYPE ON}
{$WARN UNSAFE_CODE ON}
{$WARN UNSAFE_CAST ON}
unit Voxel_Engine;

interface

uses Windows,Voxel,Normals,Palette,StdCtrls,ExtCtrls,Graphics,Math,SysUtils,Types,Constants,
     Menus,Clipbrd, forms, Dialogs, VectorTypes, NormalRoutines, ModelView;

type

   TPaletteList = record
      Data : array of String;
      Data_no : integer;
   end;

   TColorSchemes = array of packed record
        Name,Filename,By,Website : string;
        Data : array [0..255] of byte;
   end;

   TBrushShape = array[-2..2] of array[-2..2] of Boolean;

Var
   VoxelFile : TVoxelModel;
   ActiveSection : TVoxelSection;
   CurrentSection : cardinal; // TODO: CurrentSection - What is it? - HBD

  Focus: TVector3i; // Must not be TVector3b, or TModelView.Paint() won't work properly - HBD

   SpectrumMode : ESpectrumMode;
   ViewMode: EModelViewMode;
   VXLFilename : String;
   VoxelOpen : Boolean = false;
   UsedColorsOption : Boolean = false;
   BGViewColor : TColor;
   VXLTool : integer = 0;

   ActiveColor, ActiveNormal: Integer;

   mnuReopen : PMenuItem;
   PaletteList : TPaletteList;
   VXLFileExists: Boolean = false;
   Modified : Boolean = false;
   ColorSchemes : TColorSchemes;

   UsedColors : array [0..255] of boolean;
   UsedNormals : array [0..240] of boolean;

const

  VxlToolCount = 8;

  VXLTool_Pencil = 0;
  VXLTool_Line = 1;
  VXLTool_FrameRect = 2;
  VXLTool_FillRect = 3;
  VXLTool_FrameEllipse = 4;
  VXLTool_FillEllipse = 5;
  VXLTool_FloodFill = 6;
  VXLTool_FloodFill3D = 7;

  ViewNames: array[0..5] of string = (
    'Top',
    'Right',
    'Back',
    'Bottom',
    'Left',
    'Front');

Function LoadVoxel(Filename : String) : boolean;
Function NewVoxel(Game,x,y,z : integer) : boolean;
Function CleanVCol(Color : TVector3f) : TColor;
Function GetPaletteColor(Color : integer) : TColor;
function Colortogray(Color : cardinal): cardinal;
procedure SplitColor(raw: TColor; var red, green, blue: Byte);
Procedure PaintColorPalette(var Dest: TPaintBox);
Procedure PaintNormalPalette(var Dest: TPaintBox);
Function ApplyCubedNormalsToVXL(var VXL : TVoxelSection) : integer;
Function ApplyInfluenceNormalsToVXL(var VXL : TVoxelSection) : integer;
procedure velFloodFill2D(Sect: TVoxelSection; X, Y: Byte; DesiredColor: Byte; View: TModelView);
procedure velFloodFillClear2D(Sect: TVoxelSection; X, Y: Byte; View: TModelView);
procedure velFloodFill3D(Sect: TVoxelSection; X, Y, Z: Byte; DesiredColor: Byte);
procedure velFloodFillClear3D(Sect: TVoxelSection; X, Y, Z: Byte);
procedure velFloodFillMark3D(Sect: TVoxelSection; X, Y, Z: Byte; var IsOutside: array of Byte);
function velRemoveRedundantVoxels(Sect: TVoxelSection): Cardinal;
procedure velAutoNormals1(Sect: TVoxelSection);
procedure velNormalPenTool(Sect: TVoxelSection; X, Y, Z: Byte);

Procedure SetVoxelFileDefaults;
Function IsVoxelValid : Boolean;
Function HasNormalsBug : Boolean;

implementation

uses Voxel_Tools, Controls;

Function HasNormalsBug : Boolean;
var
   x,N : integer;
begin
   {$ifdef DEBUG_FILE}
   FrmMain.DebugFile.Add('VoxelEngine: HasNormalsBug');
   {$endif}
   Result := False;
   N := VoxelFile.Section[0].Tailer.Unknown;
   for x := 0 to VoxelFile.Header.NumSections -1 do
      if VoxelFile.Section[x].Tailer.Unknown <> N then
         Result := True;
end;

Function IsVoxelValid : Boolean;
begin
   {$ifdef DEBUG_FILE}
   FrmMain.DebugFile.Add('VoxelEngine: IsVoxelValid');
   {$endif}
   if AsciizToStr(VoxelFile.Header.FileType,16) <> 'Voxel Animation' then
      Result := False
   else if VoxelFile.Header.Unknown <> 1 then
      Result := False
   else
      Result := True;
end;

Function LoadVoxel(Filename: String): Boolean;
begin
   Result := false;
   try
      VoxelFile.Free;
      VoxelFile := TVoxelModel.Create;
      VoxelFile.LoadFromFile(Filename);
      CurrentSection := 0;

      ActiveSection := VoxelFile.Section[CurrentSection];
   except
      VoxelOpen := false;
      exit;
   end;

   if ActiveSection.Tailer.Unknown = 2 then
     LoadPaletteFromFile(ExtractFileDir(ParamStr(0)) + '\palettes\TS\unittem.pal')
   else
     LoadPaletteFromFile(ExtractFileDir(ParamStr(0)) + '\palettes\RA2\unittem.pal');
   VXLFilename := Filename;
   VoxelOpen := true;
   Result := true;

   Modified := false;
end;

procedure SetHeaderFileType(Name: String);
const
   MAX_LEN = 16;
var
   i: Integer;
begin
   for i:=1 to 16 do
      VoxelFile.Header.FileType[i]:=#0;
      for i := 1 to Length(Name) do
      begin
         if i > MAX_LEN then break;
         VoxelFile.Header.FileType[i] := Name[i];
      end;
end;

Procedure SetVoxelFileDefaults;
begin
  SetHeaderFileType('Voxel Animation');
  VoxelFile.Header.Unknown := 1;
  VoxelFile.Header.StartPaletteRemap := 16;
  VoxelFile.Header.EndPaletteRemap := 31;
  VoxelFile.Header.BodySize := 0;
end;

Function NewVoxel(Game,x,y,z : integer) : boolean;
begin
   try
      VoxelFile.Free;
      VoxelFile := TVoxelModel.Create;

      SetVoxelFileDefaults;
      VoxelFile.Header.NumSections := 0;
      VoxelFile.Header.NumSections2 := 0;

      CurrentSection := 0;
      VoxelFile.InsertSection(0,'Body',x,y,z);
      ActiveSection := VoxelFile.Section[CurrentSection];
      ActiveSection.Tailer.Unknown := Game;
   except
      VoxelOpen := False;
      Result := False;
      Exit;
   end;

   VXLFilename := '';
   VoxelOpen := true;
   VoxelFile.Loaded := true;
   Result := True;

   Modified := True;
end;

Function CleanVCol(Color : TVector3f) : TColor;
Var
   T : TVector3f;
begin
   T.X := Color.X;
   T.Y := Color.Y;
   T.Z := Color.Z;

   If T.X > 255 then
      T.X := 255
   else If T.X < 0 then
      T.X := 0;

   If T.Y > 255 then
      T.Y := 255
   else If T.Y < 0 then
      T.Y := 0;

   If T.Z > 255 then
      T.Z := 255
   else if T.Z < 0 then
      T.Z := 0;

   Result := RGB(trunc(T.X),trunc(T.Y),trunc(T.Z));
end;

Function GetPaletteColor(Color : integer) : TColor;
Var
   vector: TVector3f;
begin
   if Color < 0 then
   begin
      Result := BGViewColor;
      exit;
   end;

   if SpectrumMode = smColors then
      Result := VXLPalette[color]
   else
   begin
     if ActiveSection.Tailer.Unknown = 4 then
     begin
       if color >= 241 then begin
         vector.x := 0; vector.y := 0; vector.z := 0;
       end else begin
         vector.X := 128*(RA2Normals[Color].X+1);
         vector.Y := 128*(RA2Normals[Color].Y+1);
         vector.Z := 128*(RA2Normals[Color].Z+1);
       end
     end
     else begin
       if color >= 36 then begin
         vector.x := 0; vector.y := 0; vector.z := 0;
       end else begin
         vector.X := 128*(TSNormals[Color].X+1);
         vector.Y := 128*(TSNormals[Color].Y+1);
         vector.Z := 128*(TSNormals[Color].Z+1);
       end;
     end;
     Result := CleanVCol(vector);
  end;
end;

// procedure PaintView(Sect: TVoxelSection; Dest: TPaintBox; View: TModelView);

function ColorToGray(Color: Cardinal): Cardinal;
var
   temp: Byte;
begin
   temp := Byte((GetBValue(Color)*29 + GetGValue(Color)*150 + GetRValue(Color)*77) shr 8);
   Result := RGB(ord(temp),ord(temp),ord(temp));
end;

procedure SplitColor(raw: TColor; var red, green, blue: Byte);
begin
   red := (raw and $00FF0000) shr 16;
   green := (raw and $0000FF00) shr 8;
   blue := raw and $000000FF;
end;

procedure PaintColorPalette(var Dest: TPaintBox);
var
   idx: Integer;
   r: TRect;
   red, green, blue, mix: byte;
begin
  idx := 0; r.left := 0;
  while r.left < 160 do begin
    r.top := 0;
    r.Right := r.left + 20;
    while r.top < 256 do begin
      r.Bottom := r.Top + 8;
      with Dest.Canvas do begin
        Brush.Color := GetPaletteColor(idx);
        FillRect(r);
        splitColor(getpalettecolor(idx), red, green, blue);
        mix := red + green + blue;
        pen.color := RGB(128+mix, 255-mix, mix);
        if UsedColorsOption and UsedColors[idx] then
          Rectangle(r.Left,r.Top,r.Right,r.Bottom);
        if idx = activeColor then begin
          Rectangle(r.Left,r.Top,r.Right,r.Bottom);
          moveto(r.left, r.top);
          lineto(r.right, r.bottom);
          moveto(r.right, r.top);
          lineto(r.left, r.bottom);
        end;
      end;
      r.top := r.top+8;
      inc(idx);
    end;
    r.left := r.left+20;
  end;
end;

Procedure PaintNormalPalette(var Dest: TPaintBox);
var
   idx: Integer;
   r: TRect;
   red, green, blue, mix: byte;
begin
  idx := 0; r.left := 0;
  while r.left < 160 do begin
    r.top := 0;
    r.Right := r.left + 20;
    while r.top < 256 do begin
      r.Bottom := r.Top + 8;
      with Dest.Canvas do begin
        Brush.Color := GetPaletteColor(idx);
        FillRect(r);
        splitColor(getpalettecolor(idx), red, green, blue);
        mix := red + green + blue;
        pen.color := RGB(128+mix, 255-mix, mix);
        if UsedColorsOption and UsedNormals[idx] then
          Rectangle(r.Left,r.Top,r.Right,r.Bottom);
        if idx = ActiveNormal then begin
          Rectangle(r.Left,r.Top,r.Right,r.Bottom);
          moveto(r.left, r.top);
          lineto(r.right, r.bottom);
          moveto(r.right, r.top);
          lineto(r.left, r.bottom);
        end;
      end;
      r.top := r.top+8;
      inc(idx);
    end;
    r.left := r.left+20;
  end;
end;

Function ApplyCubedNormalsToVXL(var VXL : TVoxelSection) : integer;
var
Res : TApplyNormalsResult;
begin
    Res := ApplyCubedNormals(VXL,1.74,1,1,true,true,false);
    Modified := true;
    Result := Res.applied;
    MessageBox(0,pchar('AutoNormals v5.2' + #13#13 + 'Total: ' + inttostr(Res.applied) + ' voxels modified.'),'Cubed Auto Normal Results',0);
end;

Function ApplyInfluenceNormalsToVXL(var VXL : TVoxelSection) : integer;
var
   Res : TApplyNormalsResult;
begin
    Res := ApplyInfluenceNormals(VXL,3.55,1,1,true,false,false);
    Modified := true;
    Result := Res.applied;
    MessageBox(0,pchar('AutoNormals v6.1' + #13#13 + 'Total: ' + inttostr(Res.applied) + ' voxels modified.'),'Cubed Auto Normal Results',0);
end;

(***** COLORS ONLY *****)
procedure velFloodFill2D(Sect: TVoxelSection; X, Y: Byte; DesiredColor: Byte; View: TModelView);
var
  XSect: array of array of TVoxel;
  Q: array of TVector2b;
  i, j, p0, p1: Integer;
  c: TVector3i;
  v: TVoxel;
  cm: array[0..2] of PInteger; // Coordinate Mapping

  function ShouldFill(X, Y: Byte): Boolean;
  begin
    if v.Used then
    begin
      if XSect[X,Y].Used and (XSect[X,Y].Color = v.Color) then
        Result := True
      else
        Result := False;
    end
    else
    begin
      if not XSect[X,Y].Used then
        Result := True
      else
        Result := False;
    end;
  end;
  procedure NewNode(X, Y: Byte);
  begin
    Q[p1].X := X;
    Q[p1].Y := Y;
    XSect[X,Y].Used := True;
    XSect[X,Y].Color := DesiredColor;
    p1 := p1+1;
  end;

begin
  View.TranslateCoord2D(X, Y, c);
  Sect.GetVoxel(c.X,c.Y,c.Z, v);
  if v.Used and (v.Color = DesiredColor) then Exit; // Don't need to fill
  SetLength(XSect, View.Width, View.Height);
  SetLength(Q, View.Width*View.Height);
  case View.Orient of
    voTop:
    begin
      cm[0] := @i; cm[1] := @j; cm[2] := @View.Focus.Z;
    end;
    voRight:
    begin
      cm[0] := @i; cm[1] := @View.Focus.Y; cm[2] := @j;
    end;
    voBack:
    begin
      cm[0] := @View.Focus.X; cm[1] := @i; cm[2] := @j;
    end;
  end;
  if (View.Orient = voRight) or (View.Orient = voBack) then Y := View.Width-1-Y;
  if (View.Orient = voBack) then X := View.Width-1-X;
  for i:=0 to View.Width-1 do begin
    for j:=0 to View.Height-1 do begin
      XSect[i,j] := Sect.Data[cm[0]^, cm[1]^, cm[2]^];
    end;
  end;
  p0 := 0;
  p1 := 0;
  NewNode(X, Y);
  while p0 <> p1 do
  begin
    // X-1
    if (Q[p0].X > 0) and ShouldFill(Q[p0].X-1, Q[p0].Y) then
      NewNode(Q[p0].X-1, Q[p0].Y);
    // Y-1
    if (Q[p0].Y > 0) and ShouldFill(Q[p0].X, Q[p0].Y-1) then
      NewNode(Q[p0].X, Q[p0].Y-1);
    // X+1
    if (Q[p0].X < View.Width-1) and ShouldFill(Q[p0].X+1, Q[p0].Y) then
      NewNode(Q[p0].X+1, Q[p0].Y);
    // Y+1
    if (Q[p0].Y < View.Height-1) and ShouldFill(Q[p0].X, Q[p0].Y+1) then
      NewNode(Q[p0].X, Q[p0].Y+1);
    p0 := p0+1;
  end;
  for i:=0 to View.Width-1 do begin
    for j:=0 to View.Height-1 do begin
      Sect.Data[cm[0]^, cm[1]^, cm[2]^] := XSect[i,j];
    end;
  end;
  SetLength(Q, 0);
  SetLength(XSect, 0);
end;

procedure velFloodFillClear2D(Sect: TVoxelSection; X, Y: Byte; View: TModelView);
var
  XSect: array of array of TVoxel;
  Q: array of TVector2b;
  i, j, p0, p1: Integer;
  c: TVector3i;
  v: TVoxel;
  cm: array[0..2] of PInteger; // Coordinate Mapping

  function ShouldFill(X, Y: Byte): Boolean;
  begin
    if v.Used then
    begin
      if XSect[X,Y].Used and (XSect[X,Y].Color = v.Color) then
        Result := True
      else
        Result := False;
    end;
  end;
  procedure NewNode(X, Y: Byte);
  begin
    Q[p1].X := X;
    Q[p1].Y := Y;
    XSect[X,Y].Used := False;
    p1 := p1+1;
  end;

begin
  View.TranslateCoord2D(X, Y, c);
  Sect.GetVoxel(c.X,c.Y,c.Z, v);
  if not v.Used then Exit; // Don't need to fill
  SetLength(XSect, View.Width, View.Height);
  SetLength(Q, View.Width*View.Height);
  case View.Orient of
    voTop:
    begin
      cm[0] := @i; cm[1] := @j; cm[2] := @View.Focus.Z;
    end;
    voRight:
    begin
      cm[0] := @i; cm[1] := @View.Focus.Y; cm[2] := @j;
    end;
    voBack:
    begin
      cm[0] := @View.Focus.X; cm[1] := @i; cm[2] := @j;
    end;
  end;
  if (View.Orient = voRight) or (View.Orient = voBack) then Y := View.Width-1-Y;
  if (View.Orient = voBack) then X := View.Width-1-X;
  for i:=0 to View.Width-1 do begin
    for j:=0 to View.Height-1 do begin
      XSect[i,j] := Sect.Data[cm[0]^, cm[1]^, cm[2]^];
    end;
  end;
  p0 := 0;
  p1 := 0;
  NewNode(X, Y);
  while p0 <> p1 do
  begin
    // X-1
    if (Q[p0].X > 0) and ShouldFill(Q[p0].X-1, Q[p0].Y) then
      NewNode(Q[p0].X-1, Q[p0].Y);
    // Y-1
    if (Q[p0].Y > 0) and ShouldFill(Q[p0].X, Q[p0].Y-1) then
      NewNode(Q[p0].X, Q[p0].Y-1);
    // X+1
    if (Q[p0].X < View.Width-1) and ShouldFill(Q[p0].X+1, Q[p0].Y) then
      NewNode(Q[p0].X+1, Q[p0].Y);
    // Y+1
    if (Q[p0].Y < View.Height-1) and ShouldFill(Q[p0].X, Q[p0].Y+1) then
      NewNode(Q[p0].X, Q[p0].Y+1);
    p0 := p0+1;
  end;
  for i:=0 to View.Width-1 do begin
    for j:=0 to View.Height-1 do begin
      Sect.Data[cm[0]^, cm[1]^, cm[2]^] := XSect[i,j];
    end;
  end;
  SetLength(Q, 0);
  SetLength(XSect, 0);
end;

procedure velFloodFill3D(Sect: TVoxelSection; X, Y, Z: Byte; DesiredColor: Byte);
var
  Q: array of TVector3b;
  QLen: Cardinal;
  p0, p1: Cardinal;
  v: TVoxel;
  function ShouldFill(X, Y, Z: Byte): Boolean;
  begin
    if v.Used then
    begin
      if Sect.Data[X,Y,Z].Used and (Sect.Data[X,Y,Z].Color = v.Color) then
        Result := True
      else
        Result := False;
    end
    else
    begin
      if not Sect.Data[X,Y,Z].Used then
        Result := True
      else
        Result := False;
    end;
  end;
  procedure NewNode(X, Y, Z: Byte);
  begin
    Q[p1].X := X;
    Q[p1].Y := Y;
    Q[p1].Z := Z;
    Sect.Data[X,Y,Z].Used := True;
    Sect.Data[X,Y,Z].Color := DesiredColor;
    p1 := p1+1;
  end;
begin
  Sect.GetVoxel(X, Y, Z, v);
  if v.Used and (v.Color = DesiredColor) then Exit; // Don't need to fill

  QLen := Sect.Tailer.XSize * Sect.Tailer.YSize * Sect.Tailer.ZSize;

  // BFS Flood Fill
  // Space cost: 255^3*3 = 49744125 Bytes (~ 47.44 MB) at most
  // It shouldn't be a problem for modern computers
  // (But what about repainting the 3D view? It crashed my display driver!) - HBD
  try
    SetLength(Q, QLen);
  except
    MessageDlg('No enough memory to perform 3D flood fill', mtError, [mbOK], 0);
    Exit;
  end;
  p0 := 0;
  p1 := 0;
  NewNode(X, Y, Z);

  while p0 <> p1 do
  begin
    // X-1
    if (Q[p0].X > 0) and ShouldFill(Q[p0].X-1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X-1, Q[p0].Y, Q[p0].Z);
    // Y-1
    if (Q[p0].Y > 0) and ShouldFill(Q[p0].X, Q[p0].Y-1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y-1, Q[p0].Z);
    // Z-1
    if (Q[p0].Z > 0) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z-1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z-1);
    // X+1
    if (Q[p0].X < Sect.Tailer.XSize-1) and ShouldFill(Q[p0].X+1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X+1, Q[p0].Y, Q[p0].Z);
    // Y+1
    if (Q[p0].Y < Sect.Tailer.YSize-1) and ShouldFill(Q[p0].X, Q[p0].Y+1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y+1, Q[p0].Z);
    // Z+1
    if (Q[p0].Z < Sect.Tailer.ZSize-1) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z+1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z+1);
    p0 := p0+1;
  end;

  SetLength(Q, 0);
end;

procedure velFloodFillClear3D(Sect: TVoxelSection; X, Y, Z: Byte);
type
  V3Byte = record
    X, Y, Z: Byte;
  end;
var
  Q: array of V3Byte;
  QLen: Cardinal;
  p0, p1: Cardinal;
  v: TVoxel;
  function ShouldFill(X, Y, Z: Byte): Boolean;
  begin
    if Sect.Data[X,Y,Z].Used and (Sect.Data[X,Y,Z].Color = v.Color) then
      Result := True
    else
      Result := False;
  end;
  procedure NewNode(X, Y, Z: Byte);
  begin
    Q[p1].X := X;
    Q[p1].Y := Y;
    Q[p1].Z := Z;
    Sect.Data[X,Y,Z].Used := False;
    p1 := p1+1;
  end;
begin
  Sect.GetVoxel(X, Y, Z, v);
  if not v.Used then Exit; // Don't need to fill

  QLen := Sect.Tailer.XSize * Sect.Tailer.YSize * Sect.Tailer.ZSize;

  try
    SetLength(Q, QLen);
  except
    MessageDlg('No enough memory to perform 3D flood fill', mtError, [mbOK], 0);
    Exit;
  end;
  p0 := 0;
  p1 := 0;
  NewNode(X, Y, Z);

  while p0 <> p1 do
  begin
    // X-1
    if (Q[p0].X > 0) and ShouldFill(Q[p0].X-1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X-1, Q[p0].Y, Q[p0].Z);
    // Y-1
    if (Q[p0].Y > 0) and ShouldFill(Q[p0].X, Q[p0].Y-1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y-1, Q[p0].Z);
    // Z-1
    if (Q[p0].Z > 0) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z-1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z-1);
    // X+1
    if (Q[p0].X < Sect.Tailer.XSize-1) and ShouldFill(Q[p0].X+1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X+1, Q[p0].Y, Q[p0].Z);
    // Y+1
    if (Q[p0].Y < Sect.Tailer.YSize-1) and ShouldFill(Q[p0].X, Q[p0].Y+1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y+1, Q[p0].Z);
    // Z+1
    if (Q[p0].Z < Sect.Tailer.ZSize-1) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z+1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z+1);
    p0 := p0+1;
  end;

  SetLength(Q, 0);
end;

procedure velFloodFillMark3D(Sect: TVoxelSection; X, Y, Z: Byte; var IsOutside: array of Byte);
type
  V3Byte = record
    X, Y, Z: Byte;
  end;
var
  Q: array of V3Byte;
  QLen: Cardinal;
  p0, p1: Cardinal;
  idx: Cardinal;
  v: TVoxel;
  function ShouldFill(X, Y, Z: Byte): Boolean;
  var
    idx: Cardinal;
  begin
    if Sect.Data[X,Y,Z].Used then
    begin
      Result := False;
      Exit;
    end;
    idx := (X*Sect.Tailer.YSize+Y)*Sect.Tailer.ZSize+Z;
    if IsOutside[idx shr 3] and (1 shl (idx and 7)) = 0 then
      Result := True
    else
      Result := False;
  end;
  procedure NewNode(X, Y, Z: Byte);
  var
    idx: Cardinal;
  begin
    // Assert(p1 < QLen);
    Q[p1].X := X;
    Q[p1].Y := Y;
    Q[p1].Z := Z;
    idx := (X*Sect.Tailer.YSize+Y)*Sect.Tailer.ZSize+Z;
    IsOutside[idx shr 3] := IsOutside[idx shr 3] or (1 shl (idx and 7));
    p1 := p1+1;
  end;
begin
  Sect.GetVoxel(X, Y, Z, v);
  idx := (X*Sect.Tailer.YSize+Y)*Sect.Tailer.ZSize+Z;
  if v.Used or (IsOutside[idx shr 3] and (1 shl (idx and 7)) > 0) then Exit; // Already marked as outside; exit
  QLen := Sect.Tailer.XSize * Sect.Tailer.YSize * Sect.Tailer.ZSize;
  try
    SetLength(Q, QLen);
  except
    MessageDlg('No enough memory to perform redundant voxel removal', mtError, [mbOK], 0);
    Exit;
  end;
  p0 := 0;
  p1 := 0;
  NewNode(X, Y, Z);
  while p0 <> p1 do
  begin
    if (Q[p0].X > 0) and ShouldFill(Q[p0].X-1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X-1, Q[p0].Y, Q[p0].Z);
    if (Q[p0].Y > 0) and ShouldFill(Q[p0].X, Q[p0].Y-1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y-1, Q[p0].Z);
    if (Q[p0].Z > 0) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z-1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z-1);
    if (Q[p0].X < Sect.Tailer.XSize-1) and ShouldFill(Q[p0].X+1, Q[p0].Y, Q[p0].Z) then
      NewNode(Q[p0].X+1, Q[p0].Y, Q[p0].Z);
    if (Q[p0].Y < Sect.Tailer.YSize-1) and ShouldFill(Q[p0].X, Q[p0].Y+1, Q[p0].Z) then
      NewNode(Q[p0].X, Q[p0].Y+1, Q[p0].Z);
    if (Q[p0].Z < Sect.Tailer.ZSize-1) and ShouldFill(Q[p0].X, Q[p0].Y, Q[p0].Z+1) then
      NewNode(Q[p0].X, Q[p0].Y, Q[p0].Z+1);
    p0 := p0+1;
  end;
 SetLength(Q, 0);
end;

function velRemoveRedundantVoxels(Sect: TVoxelSection): Cardinal;
var
  i, j, k: Byte;
  code: Byte;
  temp: Cardinal;
  idx: Cardinal;
  YZSize: Cardinal;
  RemoveCount: Cardinal;
  IsOutside: array of Byte;
label
  Next;
begin
  temp := (Sect.Tailer.XSize*Sect.Tailer.YSize*Sect.Tailer.ZSize+7) shr 3;
  SetLength(IsOutSide, temp);
  //FillChar(IsOutside, temp, 0);
  for i:=0 to Sect.Tailer.XSize-1 do
  begin
    for j:=0 to Sect.Tailer.YSize-1 do
    begin
      velFloodFillMark3D(Sect, i, j, 0, IsOutside);
      velFloodFillMark3D(Sect, i, j, Sect.Tailer.ZSize-1, IsOutside);
    end;
  end;
  for i:=0 to Sect.Tailer.YSize-1 do
  begin
    for j:=0 to Sect.Tailer.ZSize-1 do
    begin
      velFloodFillMark3D(Sect, 0, i, j, IsOutside);
      velFloodFillMark3D(Sect, Sect.Tailer.XSize-1, i, j, IsOutside);
    end;
  end;
  for i:=0 to Sect.Tailer.ZSize-1 do
  begin
    for j:=0 to Sect.Tailer.XSize-1 do
    begin
      velFloodFillMark3D(Sect, j, 0, i, IsOutside);
      velFloodFillMark3D(Sect, j, Sect.Tailer.YSize-1, i, IsOutside);
    end;
  end;
  YZSize := Sect.Tailer.YSize * Sect.Tailer.ZSize;
  RemoveCount := 0;
  idx := 0;
  for i:=0 to Sect.Tailer.XSize-1 do
  begin
    for j:=0 to Sect.Tailer.YSize-1 do
    begin
      for k:=0 to Sect.Tailer.ZSize-1 do
      begin
        code := 0;
        // X-1
        if i > 0 then
        begin
          temp := idx - YZSize;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        // Y-1
        if j > 0 then
        begin
          temp := idx - Sect.Tailer.ZSize;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        // Z-1
        if k > 0 then
        begin
          temp := idx - 1;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        // X+1
        if i < Sect.Tailer.XSize-1 then
        begin
          temp := idx + YZSize;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        // Y+1
        if j < Sect.Tailer.YSize-1 then
        begin
          temp := idx + Sect.Tailer.ZSize;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        // Z+1
        if k < Sect.Tailer.ZSize-1 then
        begin
          temp := idx + 1;
          code := code or (IsOutside[temp shr 3] and (1 shl (temp and 7)));
        end else goto Next;
        if code = 0 then // None of the 6 adjecent voxels are marked as 'outside'; found redundant
        begin
          if Sect.Data[i,j,k].Used then Inc(RemoveCount);
          Sect.Data[i,j,k].Used := False;
        end;
      Next:
        Inc(idx);
      end;
    end;
  end;
  SetLength(IsOutside, 0);
  Result := RemoveCount;
end;

procedure velAutoNormals1(Sect: TVoxelSection);
var
  i, j, k: Byte;
  v: TVector3f;
  temp: Cardinal;
  idx: Cardinal;
  YZSize: Cardinal;
  GetNearestNormal: TGetNearestNormalFunc;
  IsOutside: array of Byte;
label
  Next;
begin
  if Sect.Tailer.Unknown = 4 then
    GetNearestNormal := GetNearestNormalRA2
  else
    GetNearestNormal := GetNearestNormalTS;
  temp := (Sect.Tailer.XSize*Sect.Tailer.YSize*Sect.Tailer.ZSize+7) shr 3;
  SetLength(IsOutSide, temp);
  for i:=0 to Sect.Tailer.XSize-1 do
  begin
    for j:=0 to Sect.Tailer.YSize-1 do
    begin
      velFloodFillMark3D(Sect, i, j, 0, IsOutside);
      velFloodFillMark3D(Sect, i, j, Sect.Tailer.ZSize-1, IsOutside);
    end;
  end;
  for i:=0 to Sect.Tailer.YSize-1 do
  begin
    for j:=0 to Sect.Tailer.ZSize-1 do
    begin
      velFloodFillMark3D(Sect, 0, i, j, IsOutside);
      velFloodFillMark3D(Sect, Sect.Tailer.XSize-1, i, j, IsOutside);
    end;
  end;
  for i:=0 to Sect.Tailer.ZSize-1 do
  begin
    for j:=0 to Sect.Tailer.XSize-1 do
    begin
      velFloodFillMark3D(Sect, j, 0, i, IsOutside);
      velFloodFillMark3D(Sect, j, Sect.Tailer.YSize-1, i, IsOutside);
    end;
  end;
  YZSize := Sect.Tailer.YSize * Sect.Tailer.ZSize;
  idx := 0;
  for i:=0 to Sect.Tailer.XSize-1 do
  begin
    for j:=0 to Sect.Tailer.YSize-1 do
    begin
      for k:=0 to Sect.Tailer.ZSize-1 do
      begin
        v.X := 0;
        v.Y := 0;
        v.Z := 0;
        // X-1
        if i > 0 then
        begin
          temp := idx - YZSize;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.X := v.X+1;
        end;
        // Y-1
        if j > 0 then
        begin
          temp := idx - Sect.Tailer.ZSize;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.Y := v.Y+1;
        end;
        // Z-1
        if k > 0 then
        begin
          temp := idx - 1;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.Z := v.Z+1;
        end;
        // X+1
        if i < Sect.Tailer.XSize-1 then
        begin
          temp := idx + YZSize;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.X := v.X-1;
        end;
        // Y+1
        if j < Sect.Tailer.YSize-1 then
        begin
          temp := idx + Sect.Tailer.ZSize;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.Y := v.Y-1;
        end;
        // Z+1
        if k < Sect.Tailer.ZSize-1 then
        begin
          temp := idx + 1;
          if IsOutside[temp shr 3] and (1 shl (temp and 7)) = 0 then v.Z := v.Z-1;
        end;
        if not ((v.X=0) and (v.Y=0) and (v.Z=0)) then
          Sect.Data[i,j,k].Normal := GetNearestNormal(v);
      Next:
        Inc(idx);
      end;
    end;
  end;
  SetLength(IsOutside, 0);
end;

procedure velNormalPenTool(Sect: TVoxelSection; X, Y, Z: Byte);
var
  i, j, k: Integer;
  v: TVector3f;
begin
  v := CreateVector3f(0, 0, 0);
  for i:=X-1 to X+1 do
  begin
    if i in [0..Sect.Tailer.XSize-1] then
    for j:=Y-1 to Y+1 do
      begin
      if j in [0..Sect.Tailer.YSize-1] then
      for k:=Z-1 to Z+1 do
      begin
        if k in [0..Sect.Tailer.ZSize-1] then
        begin
          if Sect.Data[i,j,k].Used then
          begin
            v.X := v.X + X-i;
            v.Y := v.Y + Y-j;
            v.Z := v.Z + Z-k;
          end;
        end;
      end;
    end;
  end;
  if not ((v.X=0) and (v.Y=0) and (v.Z=0)) then
    PByte(@Sect.Data[X,Y,Z])^ := GetNearestNormalRA2(v);
end;

begin
   // Load default palette
   LoadPaletteFromFile(ExtractFileDir(ParamStr(0)) + '\palettes\TS\unittem.pal');

   BGViewColor := RGB(140,170,235);
   VoxelFile := TVoxelModel.Create;
   VoxelFile.InsertSection(0,'Dummy',1,1,1);
   ActiveSection := VoxelFile.Section[0];

   SpectrumMode := smColors;
   ViewMode := vmEmphasiseDepth;

end.
